/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "ola_login.h"
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include "securec.h"
#include "cJSON.h"
#include "ola_log.h"
#include "ola_error.h"
#include "ola_link.h"
#include "ola_def.h"
#include "ola_base_utils.h"
#include "ola_data.h"
#include "ola_sdk.h"
#include "ola_cloud_token.h"

typedef enum {
    LOGIN_STATE_SEND_LOGIN_REQUEST,
    LOGIN_STATE_REPORT_SERVICE,
    LOGIN_STATE_WAIT_RESPONSE,
    LOGIN_STATE_FINISH,
    LOGIN_STATE_FAILED,
} OlaLoginState;

static OlaLoginState g_loginState = LOGIN_STATE_SEND_LOGIN_REQUEST;
static unsigned long long g_startWaitTime = 0;

static void OlaSetLoginState(OlaLoginState state)
{
    g_loginState = state;
    OlaLogInfo("set login state[%d]", state);
}

static OlaLoginState OlaGetLoginState(void)
{
    return g_loginState;
}

void OlaLoginInit(void)
{
    OlaSetLoginState(LOGIN_STATE_SEND_LOGIN_REQUEST);
    g_startWaitTime = 0;
}

static int OlaParseLoginErrCode(cJSON *json)
{
    cJSON *errCode = cJSON_GetObjectItem(json, OLA_STRING_ERRCODE);
    if (errCode == NULL) {
        OlaLogError("no errCode");
        return OLA_CJSON_ERR_PARSE;
    }

    if (errCode->valueint == OLA_OK) {
        return OLA_OK;
    }

    OlaLogError("parse login errcode:[%d]", errCode->valueint);

    switch (errCode->valueint) {
        case OLA_LOGIN_ERR: /* 设备登录失败 */
            return OLA_LOGIN_ERR;
        default:
            return OLA_LOGIN_OTHERS;
    }

    return OLA_OK;
}

static int OlaParseLoginToken(cJSON *json)
{
    char *accessToken = OlaGetJsonString(json, OLA_STRING_ACCESS_TOKEN);
    if ((accessToken == NULL) || (strlen((char *)accessToken) == 0) ||
        (strlen((char *)accessToken) > OLA_LOGIN_ACCESS_TOKEN_LEN)) {
        OlaLogError("parse access token err");
        return OLA_CJSON_ERR_GET_STRING;
    }

    char *refreshToken = OlaGetJsonString(json, OLA_STRING_REFRESH_TOKEN);
    if ((refreshToken == NULL) || (strlen((char *)refreshToken) == 0) ||
        (strlen((char *)refreshToken) > OLA_LOGIN_REFRESH_TOKEN_LEN)) {
        OlaLogError("parse refresh token err");
        return OLA_CJSON_ERR_GET_STRING;
    }

    int timeout = 0;
    int ret = OlaGetJsonNumber(json, OLA_STRING_TIMEOUT, &timeout);
    if ((ret != OLA_OK) || (timeout < 0)) {
        OlaLogError("parse timeout err");
        return OLA_CJSON_ERR_GET_NUMBER;
    }
    ret = CloudSetAccessToken(accessToken);
    if (ret != OLA_OK) {
        OlaLogError("access token init error");
        return ret;
    }
    ret = CloudSetRefreshToken(refreshToken);
    if (ret != OLA_OK) {
        OlaLogError("refresh token init error");
        return ret;
    }

    /* timeout时间 由s转换位ms */
    CloudSetTokenExpireTime((unsigned long long)timeout);
    return OLA_OK;
}

static int OlaProcDeviceLoginRsp(const OlaData *payload)
{
    cJSON *json = cJSON_Parse((const char *)payload->data);
    if (json == NULL) {
        OlaLogError("json parse err");
        return OLA_CJSON_ERR_PARSE;
    }

    int ret = OlaParseLoginErrCode(json);
    if (ret != OLA_OK) {
        OlaLogError("parse errCode err");
        goto FAILED;
    }

    ret = OlaParseLoginToken(json);
    if (ret != OLA_OK) {
        OlaLogError("parse token and timeout err");
        goto FAILED;
    }
    cJSON_Delete(json);
    return OLA_OK;
FAILED:
    cJSON_Delete(json);
    return ret;
}

static void OlaCoapLoginRspHandler(OlaCoapSession *session, const OlaCoapPacket *pkt)
{
    /* 处理云端回复同时驱动状态机 */
    if ((session == NULL) || (pkt == NULL) || !OlaIsValidCloudSession(session)) {
        OlaLogWarning("invalid param");
        return;
    }
    if (OlaGetLoginState() != LOGIN_STATE_WAIT_RESPONSE) {
        OlaLogWarning("response in invalid state");
        return;
    }

    if (pkt->header.code != OLA_COAP_RESPONSE_CODE_CONTENT) {
        OlaLogWarning("coap response error[%d]", pkt->header.code);
        goto FAILED;
    }

    if ((pkt->payload.data == NULL) || (pkt->payload.len == 0)) {
        OlaLogWarning("empty response");
        goto FAILED;
    }

    if (OlaProcDeviceLoginRsp(&pkt->payload) != OLA_OK) {
        OlaLogWarning("login resp error");
        goto FAILED;
    }
    OlaSetLoginState(LOGIN_STATE_REPORT_SERVICE);
    return;
FAILED:
    OlaSetLoginState(LOGIN_STATE_FAILED);
}

static int OlaAddDevIdAndSecretInfoToJson(cJSON *json)
{
    OlaData devid = { NULL, OLA_DEV_REGISTER_DEVID_LEN };
    if (OlaGetData(OLA_DATA_TYPE_DEV_ID, &devid) != OLA_OK) {
        OlaLogError("get devid error");
        return OLA_DATA_ERR_GET_DATA;
    }

    if ((devid.data == NULL) || (strlen((char *)devid.data) == 0) ||
        (strlen((char *)devid.data) > OLA_DEV_REGISTER_DEVID_LEN)) {
        OlaLogError("devid len error");
        return OLA_DATA_ERR_GET_DATA;
    }

    unsigned char secret[OLA_DEV_REGISTER_SECRET_LEN + 1] = {0};
    OlaData secData = { secret, OLA_DEV_REGISTER_SECRET_LEN };
    if (OlaGetData(OLA_DATA_TYPE_SECRET, &secData) != OLA_OK) {
        (void)memset_s(secret, sizeof(secret), 0, sizeof(secret));
        return OLA_DATA_ERR_GET_DATA;
    }

    if (secData.len == 0) {
        (void)memset_s(secret, sizeof(secret), 0, sizeof(secret));
        return OLA_DATA_ERR_GET_DATA;
    }

    if ((secData.data == NULL) || (strlen((char *)secData.data) == 0) ||
        (strlen((char *)secData.data) > OLA_DEV_REGISTER_SECRET_LEN)) {
        OlaLogError("secret len error");
        (void)memset_s(secret, sizeof(secret), 0, sizeof(secret));
        return OLA_DATA_ERR_GET_DATA;
    }

    if ((cJSON_AddStringToObject(json, OLA_STRING_DEVID, (char *)devid.data) == NULL) ||
        (cJSON_AddStringToObject(json, OLA_STRING_SECRET, (char *)secData.data) == NULL)) {
        OlaLogError("add string error");
        (void)memset_s(secret, sizeof(secret), 0, sizeof(secret));
        return OLA_CJSON_ERR_ADD_STRING;
    }
    (void)memset_s(secret, sizeof(secret), 0, sizeof(secret));

    return OLA_OK;
}

static int OlaBuildLoginRequest(OlaData *data, OlaCoapParam *param)
{
    cJSON *payloadJson = cJSON_CreateObject();
    if (payloadJson == NULL) {
        OlaLogError("create json error");
        return OLA_CJSON_ERR_CREATE_OBJECT;
    }

    char *payload = NULL;
    int ret = OlaAddDevIdAndSecretInfoToJson(payloadJson);
    if (ret != OLA_OK) {
        goto FAILED;
    }

    payload = cJSON_PrintUnformatted(payloadJson);
    if (payload == NULL) {
        OlaLogError("get json payload error");
        ret = OLA_CJSON_ERR_PRINT_UNFORMATTED;
        goto FAILED;
    }
    data->data = (unsigned char *)payload;
    data->len = strlen(payload);
    OlaBuildCloudRequestCoapParam(param, OLA_COAP_URI_LOGIN, OlaCoapLoginRspHandler, false);
    cJSON_Delete(payloadJson);
    return OLA_OK;
FAILED:
    cJSON_Delete(payloadJson);
    OlaPutNullAfterFree((void **)&payload);
    return ret;
}

static int SendLoginRequest(void)
{
    /* 发送注册登录请求 */
    OlaData sendData = { NULL, 0 };
    OlaCoapParam param;
    (void)memset_s(&param, sizeof(OlaCoapParam), 0, sizeof(OlaCoapParam));
    int ret = OlaBuildLoginRequest(&sendData, &param);
    if (ret != OLA_OK) {
        OlaLogWarning("build login request error");
        goto EXIT;
    }

    ret = OlaSendDataToCloud(&sendData, &param);
    if (ret != OLA_OK) {
        OlaLogWarning("send error");
        goto EXIT;
    }

EXIT:
    OlaPutNullAfterFree((void **)&sendData.data);
    return ret;
}

static void CoapDeviceServiceResponseHandler(OlaCoapSession *session, const OlaCoapPacket *pkt)
{
    if ((session == NULL) || (pkt == NULL) || !OlaIsValidCloudSession(session)) {
        OlaLogWarning("invalid param");
        return;
    }
    if (pkt->header.code != OLA_COAP_RESPONSE_CODE_CONTENT) {
        OlaLogWarning("report device services error[%d]", pkt->header.code);
        OlaSetLoginState(LOGIN_STATE_FAILED);
    } else {
        OlaLogInfo("report device services success");
        OlaSetLoginState(LOGIN_STATE_FINISH);
    }
}
static int BuildDeviceServiceReport(OlaData *data, OlaCoapParam *param)
{
    if (OlaGetDeviceData(OLA_DATA_TYPE_JSON_DEVICE_MODEL, data) != OLA_OK) {
        OlaLogError("get outside device model failed");
        return OLA_ERROR;
    }
    if ((data->len == 0) || (data->data == NULL)) {
        OlaLogError("invalid model data");
        return OLA_ERROR;
    }
    OlaBuildCloudRequestCoapParam(param, OLA_COAP_URI_SERVICES, CoapDeviceServiceResponseHandler, true);
    return OLA_OK;
}

/* 上报所有服务信息 */
static int OlaReportAllService(void)
{
    int ret;
    OlaData sendData = {NULL, 0};
    OlaCoapParam param;
    (void)memset_s(&param, sizeof(OlaCoapParam), 0, sizeof(OlaCoapParam));
    ret = BuildDeviceServiceReport(&sendData, &param);
    if (ret != OLA_OK) {
        OlaLogWarning("build device services error");
        return ret;
    }

    ret = OlaSendDataToCloud(&sendData, &param);
    if (ret != OLA_OK) {
        OlaLogWarning("send device services error");
        goto EXIT;
    }
EXIT:
    OlaPutNullAfterFree((void **)&sendData.data);
    return ret;
}

int OlaLoginProcess(void)
{
    int ret = OLA_STATE_RUNING;
    int errcode;
    switch (OlaGetLoginState()) {
        case LOGIN_STATE_SEND_LOGIN_REQUEST:
            OlaNotifySdkEvent(OLA_EVENT_CODE_M2M_LOGIN_START);
            errcode = SendLoginRequest();
            if (errcode == OLA_OK) {
                OlaSetLoginState(LOGIN_STATE_WAIT_RESPONSE);
            } else {
                OlaLogWarning("send login request error");
                ret = OLA_ERROR;
            }
            break;
        case LOGIN_STATE_REPORT_SERVICE:
            errcode = OlaReportAllService();
            if (errcode == OLA_OK) {
                OlaSetLoginState(LOGIN_STATE_WAIT_RESPONSE);
            } else {
                OlaLogWarning("send report service error");
                ret = OLA_ERROR;
            }
            break;
        case LOGIN_STATE_WAIT_RESPONSE:
            errcode = OlaWaitCloudResponse(&g_startWaitTime);
            if (errcode != OLA_OK) {
                OlaLogWarning("wait response error");
                ret = OLA_ERROR;
            }
            break;
        case LOGIN_STATE_FINISH:
            OlaNotifySdkEvent(OLA_EVENT_CODE_M2M_LOGIN_SUCCESS);
            ret = OLA_OK;
            break;
        case LOGIN_STATE_FAILED:
            OlaNotifySdkEvent(OLA_EVENT_CODE_M2M_LOGIN_FAIL);
            ret = OLA_ERROR;
            break;
        default:
            OlaLogWarning("invalid login state");
            ret = OLA_ERROR;
            break;
    }
    return ret;
}